/*
 * AssertionConsumerService.java
 * 
 * Version information
 * 
 * Mar 17, 2006
 * 
 * Copyright (c) AmSoft Systems, 2006
 */
package net.amsoft.iservice.icontact.webapp.struts.action;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.amsoft.iservice.icontact.hibernate.Message;
import net.amsoft.iservice.icontact.hibernate.dataObject.LoginData;
import net.amsoft.iservice.icontact.hibernate.dataObject.RequestData;
import net.amsoft.iservice.icontact.service.IContact;
import static net.amsoft.iservice.icontact.util.IContactConstants.*;
import net.amsoft.iservice.icontact.webapp.struts.form.RequestForm;
import net.amsoft.iservice.isso.client.ISSOClient;
import net.amsoft.iservice.isso.client.SAMLDataObject;
import net.amsoft.iservice.isso.client.ISSOClient.IDPClientException;
import net.amsoft.iservice.util.IServiceUtil;
import net.amsoft.iservice.util.ResolverClient;

import org.apache.log4j.Logger;
import org.apache.soap.Envelope;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.action.ActionMessage;
import org.apache.struts.action.ActionMessages;
import org.apache.struts.util.MessageResources;
import com.janrain.openid.consumer.Consumer;
import com.janrain.openid.consumer.Response;
import com.janrain.openid.consumer.StatusCode;
import com.janrain.openid.store.MemoryStore;
/**
 * The SAML standard defines a framework for exchanging security information
 * between software entities on the Web. SAML security is based on the
 * interaction of asserting and relying parties. Class AssertionConsumer ensures
 * the secure communication between the entities while saving the information.
 * 
 * @author rahul
 * @author mohit
 * 
 */
public class AssertionConsumerService extends BaseAction {

    private static final Logger oLogger = Logger
            .getLogger(AssertionConsumerService.class);
    private static final String storeKey = "openid.store";
    private static final String sessionKey = "openid.session";

    public ActionForward process(ActionMapping oMapping,
            ActionForm oActionForm, HttpServletRequest oRequest,
            HttpServletResponse oResponse) throws Exception {
        oLogger.info("process() : entry");
        ActionMessages oMsgs = new ActionErrors();
        LoginData oLoginData = (LoginData) oRequest.getSession(false)
                .getAttribute(SESSION_LOGIN_OBJ);
        String sSAMLArtifactDoc = oRequest
                .getParameter(ISSOClient.PARAM_SAML_ART);
        String sRelayState = oRequest
                .getParameter(ISSOClient.PARAM_RELAY_STATE);
        // Added for OpenId Authentication
        String sMode = oRequest.getParameter("openid.mode");
        SAMLDataObject oReceivedSAMLData = null;
        if (sSAMLArtifactDoc != null) {
            oReceivedSAMLData = ISSOClient
                    .getValuesFromSAMLArtifact(sSAMLArtifactDoc);
        }

        if (oRequest.getSession(false).getAttribute(SESSION_CONTACT_REQ) != null) {
            // Determine if the response is for SAML or OpenId
            if ((sSAMLArtifactDoc == null && sRelayState == null)
                    && sMode != null) {
                oLogger.debug("process() : SAMLArtifactDoc: "
                        + sSAMLArtifactDoc + ", RelayState: " + sRelayState
                        + ", Mode: " + sMode);
                return handleContactRequestOpenID(oRequest, oMapping);
            }
            // artifact received for placing contact request
            return handleContactRequest(oRequest, oReceivedSAMLData,
                    sRelayState, oMapping);
        }

        if (!sRelayState.equals(oLoginData.getRelayState())
                || !oLoginData.getProviderIDHash().equals(
                        oReceivedSAMLData.getProviderIDHash())) {
            // invalid relaystate or invalid providerID
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    ERR_INVALID, "request"));
            saveErrors(oRequest.getSession(false), oMsgs);
            oLogger.warn("process(): RelayState=" + oLoginData.getRelayState()
                    + ", Provider Id Hash=" + oLoginData.getProviderIDHash());
            return oMapping.findForward(FWD_SUCCESS);
        }
        String sArtifactConsumerService = ResolverClient.getMetaDataIDP(
                oLoginData.getIDPMetaDataEP())
                .getArtifactResolutionServiceURL();
        String sArtifactResolveXML = ISSOClient
                .createSAMLArtifactResolveRequest(oReceivedSAMLData
                        .getArtifact());

        Envelope oEnvelope = ISSOClient.createSOAPEnvelope(sArtifactResolveXML);
        // TODO TO BE REMOVED in case of secure certs
        MessageResources resources = getResources(oRequest);
        String cert_path = resources.getMessage("cert.path");
        String keystore_pwd = resources.getMessage("cert.pwd");
        boolean isSecure = Boolean.parseBoolean(resources.getMessage("secure"));
        Envelope oReceivedEnvelope = null;
        if (isSecure) {
            oReceivedEnvelope = ISSOClient.sendAndReceiveSOAPEnvelope(
                    oEnvelope, sArtifactConsumerService);
        } else {
            oReceivedEnvelope = ISSOClient.sendAndReceiveSOAPEnvelope(
                    oEnvelope, sArtifactConsumerService, cert_path,
                    keystore_pwd);
        }
        oLogger.debug("process() : Soap envelope received : "
                + oReceivedEnvelope);
        String sArtifactResponseXML = ISSOClient
                .getArtifactResponseFromSOAPEnvelpe(oReceivedEnvelope);
        SAMLDataObject oSAMLAuthResponseData = ISSOClient
                .getAuthnResponseValuesFromArtifactResponse(sArtifactResponseXML);

        if (!oLoginData.getGlobalInumber().equals(
                oSAMLAuthResponseData.getSubjectNameIDNameQualifier())
                || !oSAMLAuthResponseData.getSAMLStatus().equals(
                        ISSOClient.SAML_STATUS_SUCCESS)) {
            // required principal is not logged in
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    ERR_INVALID, "request"));
            saveErrors(oRequest.getSession(false), oMsgs);
            oLogger.warn("process(): expected GIN:"
                    + oLoginData.getGlobalInumber() + ", received:"
                    + oSAMLAuthResponseData.getSubjectNameIDNameQualifier());
            oLogger.warn("process(): received STATUS:"
                    + oSAMLAuthResponseData.getSAMLStatus());
            return oMapping.findForward(FWD_SUCCESS);
        }
        // successful login
        oLoginData.setLoginStatus(true);
        IContact.loadSynonyms(oLoginData);
        oRequest.getSession(false).setAttribute(SESSION_SYN,
                oLoginData.getLoggedInSynonym());
        oLoginData.setSAMLData(oSAMLAuthResponseData);
        oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                MSG_LOGIN_SUCCESS));
        saveErrors(oRequest.getSession(false), oMsgs);
        oLogger.info("process(): successfull login");
        return oMapping.findForward(FWD_SUCCESS);

    }

    /**
     * This method is used to handle the contact request recieved from some
     * ibroker through openid server.
     * 
     * @param oRequest
     *            Recieved HTTP Request
     * @param oMapping
     *            Map of Action to be taken
     * @return
     */
    private ActionForward handleContactRequestOpenID(
            HttpServletRequest oRequest, ActionMapping oMapping) {
        oLogger.info("handleContactRequestOpenID() : entry");

        ActionMessages oMsgs = new ActionErrors();
        // get a Consumer instance
        Consumer c = getConsumer(oRequest);

        // convert the argument map into the form the library uses with a handy
        // convenience function
        Map query = Consumer.filterArgs(oRequest.getParameterMap());

        // Check the arguments to see what the response was.
        Response response = c.complete(query);

        StatusCode status = response.getStatus();
        String sMode = oRequest.getParameter("openid.mode");

        // Check if the response was successfull or not
        if (status != StatusCode.SUCCESS) {
            oLogger.warn("handleContactRequestOpenID(): mode=" + sMode);
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    ERR_AUTHN_FAILED));
            RequestForm oRequestForm = (RequestForm) oRequest.getSession(false)
                    .getAttribute(SESSION_CONTACT_REQ);
            oRequest.setAttribute("RequestForm", oRequestForm);
            saveErrors(oRequest.getSession(false), oMsgs);
            return oMapping.findForward(FWD_MSG);
        }
        oLogger.info("handleContactRequest(): Successfull Authentication");

        return saveContactRequest(oRequest, oMapping);
    }

    /*
     * This method gets a <code>com.janrain.openid.consumer.Consumer</code>
     * instance appropriate for use in this servlet.
     */
    private Consumer getConsumer(HttpServletRequest oRequest) {
        // pull the memory store out of the context
        MemoryStore ms = (MemoryStore) oRequest.getSession().getAttribute(
                storeKey);

        // fetch/create a session <code>Map</code> for the consumer's use
        Map session = (Map) oRequest.getSession().getAttribute(sessionKey);
        if (session == null) {
            session = new HashMap();
            oRequest.getSession().setAttribute(sessionKey, session);
        }

        return new Consumer(session, ms);
    }

    /**
     * This function used to handle the artifact document, received against
     * authentication. request sent while placing contact request
     * 
     * @param oRequest
     *            Recieved HTTP Request
     * @param oReceivedSAMLData
     *            SAML Data recieved
     * @param sRelayState
     *            Relay State
     * @param oMapping
     *            Map of Action to be taken
     * @return
     */
    private ActionForward handleContactRequest(HttpServletRequest oRequest,
            SAMLDataObject oReceivedSAMLData, String sRelayState,
            ActionMapping oMapping) {
        oLogger.info("handleContactRequest() : entry");
        RequestData oRequestData = (RequestData) oRequest.getSession(false)
                .getAttribute(SESSION_REQ_DATA);

        ActionMessages oMsgs = new ActionErrors();

        if (!sRelayState.equals(oRequestData.getRelayState())
                || !oRequestData.getProviderIDHash().equals(
                        oReceivedSAMLData.getProviderIDHash())) {
            oLogger.debug("handleContactRequest(): Relay State="
                    + oRequestData.getRelayState() + ",Provider Id Hash="
                    + oRequestData.getProviderIDHash());
            RequestForm oRequestForm = (RequestForm) oRequest.getSession(false)
                    .getAttribute(SESSION_CONTACT_REQ);
            oRequest.setAttribute("RequestForm", oRequestForm);
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    ERR_AUTHN_FAILED));
            saveErrors(oRequest.getSession(false), oMsgs);
            return oMapping.findForward(FWD_MSG);
        }
        String sArtifactConsumerService = ResolverClient.getMetaDataIDP(
                oRequestData.getIDPMetaDataEP())
                .getArtifactResolutionServiceURL();

        SAMLDataObject oSAMLAuthResponseData = null;
        try {
            String sArtifactResolveXML = ISSOClient
                    .createSAMLArtifactResolveRequest(oReceivedSAMLData
                            .getArtifact());
            Envelope oEnvelope = ISSOClient
                    .createSOAPEnvelope(sArtifactResolveXML);

            // TODO TO BE REMOVED
            MessageResources resources = getResources(oRequest);
            String cert_path = resources.getMessage("cert.path");
            String keystore_pwd = resources.getMessage("cert.pwd");
            boolean isSecure = Boolean.parseBoolean(resources
                    .getMessage("secure"));
            Envelope oReceivedEnvelope = null;
            if (isSecure) {
                oReceivedEnvelope = ISSOClient.sendAndReceiveSOAPEnvelope(
                        oEnvelope, sArtifactConsumerService);
            } else {
                oReceivedEnvelope = ISSOClient.sendAndReceiveSOAPEnvelope(
                        oEnvelope, sArtifactConsumerService, cert_path,
                        keystore_pwd);
            }
            oLogger.debug("handleContactRequest(): Soap envelope received: "
                    + oReceivedEnvelope);
            String sArtifactResponseXML = ISSOClient
                    .getArtifactResponseFromSOAPEnvelpe(oReceivedEnvelope);
            oSAMLAuthResponseData = ISSOClient
                    .getAuthnResponseValuesFromArtifactResponse(sArtifactResponseXML);
        } catch (IDPClientException oIdpe) {
            oLogger.error("handleContactRequest(): Exception : "
                    + oIdpe.getMessage() + ", SAML parsing exception. "
                    + "Exiting, Forward to iname page");
            return oMapping.findForward(FWD_MSG);
        }

        if (!oRequestData.getGlobalInumber().equals(
                oSAMLAuthResponseData.getSubjectNameIDNameQualifier())
                || !oSAMLAuthResponseData.getSAMLStatus().equals(
                        ISSOClient.SAML_STATUS_SUCCESS)) {
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    ERR_AUTHN_FAILED));
            saveErrors(oRequest.getSession(false), oMsgs);
            oLogger.warn("handleContactRequest(): Expected GIN:"
                    + oRequestData.getGlobalInumber() + ", Received:"
                    + oSAMLAuthResponseData.getSubjectNameIDNameQualifier()
                    + ", Status:" + oSAMLAuthResponseData.getSAMLStatus());
            // TODO return to contact page
            return oMapping.findForward(FWD_MSG);
        }
        oLogger
                .info("handleContactRequest(): exit, Successfull Authentication");

        return saveContactRequest(oRequest, oMapping);
    }

    /**
     * This method saves the contact request to the database and send out the
     * notification mail to the receiving i-name
     * 
     * @param oRequest
     *            Recieved HTTP Request
     * @param oMapping
     *            Action to be taken
     * @return Action forward of the page where the control has to be redirected
     *         next
     */
    private ActionForward saveContactRequest(HttpServletRequest oRequest,
            ActionMapping oMapping) {
        RequestForm oRequestForm = (RequestForm) oRequest.getSession(false)
                .getAttribute(SESSION_CONTACT_REQ);
        ActionMessages oMsgs = new ActionErrors();
        try {
            // // saving request
            String sHash = IServiceUtil.generateUniqueID();
            String sRef = (String) oRequest.getSession(false).getAttribute(
                    SESSION_REF);
            Message oSavedMsg = IContact.saveMessages(
                    oRequestForm.getSynonym(), sHash, sRef, oRequestForm
                            .getSenderEmail(), oRequestForm.getSenderName(),
                    oRequestForm.getSenderIname(), oRequestForm.getMsg(),
                    ACTIVE);

            sendNotificationEmail(oSavedMsg, oRequest);
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    MSG_REQ_SENT));
            saveErrors(oRequest.getSession(false), oMsgs);
            oLogger.info("handleContactRequest(): Request Saved");
            return oMapping.findForward(FWD_MSG);

        } catch (Exception e) {
            oLogger.warn("handleContactRequest() : Exception : "
                    + e.getMessage() + ", Request Saving failed");
            oMsgs.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage(
                    ERR_AUTHN_FAILED));
            saveErrors(oRequest.getSession(false), oMsgs);
            // TODO return to contact page
            return oMapping.findForward(FWD_MSG);
        } finally {
            oRequest.getSession(false).removeAttribute(SESSION_CONTACT_REQ);
            oRequest.getSession(false).removeAttribute(SESSION_REQ_DATA);
            oRequest.getSession(false).removeAttribute(SESSION_REF);
        }
    }
}
